﻿using System;
using System.Linq;
using System.Text;
using Ooaavee.Events;
using System.Collections.ObjectModel;

namespace Ooaavee.TestTools
{
    /// <summary>
    /// Verifies event related conditions in unit tests using true/false propositions.
    /// </summary>
    public static class EventAssert
    {
        /// <summary>
        /// Verifies that the event count is expected. The assertion fails if the event count is not expected.
        /// </summary>
        /// <param name="expected">This is the event count the unit test expects.</param>
        /// <param name="watcher">The EventWatcher to test. The EventWatcher must not be disposed.</param>
        public static void IsEventCount(int expected, EventWatcher watcher)
        {
            IsEventCount(expected, watcher, null);
        }

        /// <summary>
        /// Verifies that the event count is expected. The assertion fails if the event count is not expected.
        /// Displays a message if the assertion fails.
        /// </summary>
        /// <param name="expected">This is the event count the unit test expects.</param>
        /// <param name="watcher">The EventWatcher to test. The EventWatcher must not be disposed.</param>
        /// <param name="message">A message to display if the assertion fails. This message can be seen in the unit test results.</param>
        public static void IsEventCount(int expected, EventWatcher watcher, string message)
        {
            if (watcher == null)
            {
                throw new ArgumentNullException("watcher");
            }

            if (watcher.IsDisposed)
            {
                throw new ArgumentException("EventWatcher must not be disposed.", "watcher");
            }            

            int actual = watcher.GetResults().EventCount;

            bool passed = false;
            if (expected == actual)
            {
                passed = true;
            }

            if (passed == false)
            {
                AssertGateway.Fail(GetAssertFailedMessage(string.Format("EventAssert.IsEventCount failed. Expected:<{0}>. Actual:<{1}>.", expected, actual), message));
            }
        }

        /// <summary>
        /// Verivies that event was raised. The assertion fails if the event was not raised.
        /// </summary>
        /// <param name="eventName">This is the name of raised event the unit test expects.</param>
        /// <param name="watcher">The EventWatcher to test. The EventWatcher must not be disposed.</param>
        public static void WasRaised(string eventName, EventWatcher watcher)
        {
            WasRaised(eventName, watcher, null);
        }

        /// <summary>
        /// Verivies that event was raised. The assertion fails if the event was not raised.
        /// Displays a message if the assertion fails.
        /// </summary>
        /// <param name="eventName">This is the name of raised event the unit test expects.</param>
        /// <param name="watcher">The EventWatcher to test. The EventWatcher must not be disposed.</param>
        /// <param name="message">A message to display if the assertion fails. This message can be seen in the unit test results.</param>
        public static void WasRaised(string eventName, EventWatcher watcher, string message)
        {
            if (eventName == null)
            {
                throw new ArgumentNullException("eventName");
            }

            if (watcher == null)
            {
                throw new ArgumentNullException("watcher");
            }

            if (watcher.IsDisposed)
            {
                throw new ArgumentException("EventWatcher must not be disposed.", "watcher");
            }            

            bool passed = watcher.GetResults().ReceivedEvents.Any(evt => string.Equals(eventName, evt.Name, StringComparison.CurrentCulture));

            if (passed == false)
            {
                AssertGateway.Fail(GetAssertFailedMessage("EventAssert.WasRaised failed.", message));                
            }
        }

        /// <summary>
        /// Verifies that event was not raised. The assertion fails if the event was raised.
        /// </summary>
        /// <param name="eventName">This is the name of unraised event the unit test expects.</param>
        /// <param name="watcher">The EventWatcher to test. The EventWatcher must not be disposed.</param>
        public static void WasNotRaised(string eventName, EventWatcher watcher)
        {
            WasNotRaised(eventName, watcher, null);
        }

        /// <summary>
        /// Verifies that event was not raised. The assertion fails if the event was raised.
        /// Displays a message if the assertion fails.
        /// </summary>
        /// <param name="eventName">This is the name of unraised event the unit test expects.</param>
        /// <param name="watcher">The EventWatcher to test. The EventWatcher must not be disposed.</param>
        /// <param name="message">A message to display if the assertion fails. This message can be seen in the unit test results.</param>
        public static void WasNotRaised(string eventName, EventWatcher watcher, string message)
        {
            if (eventName == null)
            {
                throw new ArgumentNullException("eventName");
            }

            if (watcher == null)
            {
                throw new ArgumentNullException("watcher");
            }

            if (watcher.IsDisposed)
            {
                throw new ArgumentException("EventWatcher must not be disposed.", "watcher");
            }            

            bool passed = false;

            bool raised = watcher.GetResults().ReceivedEvents.Any(evt => string.Equals(eventName, evt.Name, StringComparison.CurrentCulture));
            if (raised == false)
            {
                passed = true;
            }

            if (passed == false)
            {
                AssertGateway.Fail(GetAssertFailedMessage("EventAssert.WasNotRaised failed.", message));                                
            }
        }

        /// <summary>
        /// Verifies that events were raised in the sequence. The assertion fails if events were not raised in the sequence.
        /// </summary>
        /// <param name="expectedSequence">This is the event sequence the unit test expects.</param>
        /// <param name="watcher">The EventWatcher to test. The EventWatcher must not be disposed.</param>
        public static void WereRaisedInSequence(string[] expectedSequence, EventWatcher watcher)
        {
            WereRaisedInSequence(expectedSequence, watcher, null);
        }

        /// <summary>
        /// Verifies that events were raised in the sequence. The assertion fails if events were not raised in the sequence.
        /// Displays a message if the assertion fails.
        /// </summary>
        /// <param name="expectedSequence">This is the event sequence the unit test expects.</param>
        /// <param name="watcher">The EventWatcher to test. The EventWatcher must not be disposed.</param>
        /// <param name="message">A message to display if the assertion fails. This message can be seen in the unit test results.</param>
        public static void WereRaisedInSequence(string[] expectedSequence, EventWatcher watcher, string message)
        {
            if (expectedSequence == null)
            {
                throw new ArgumentNullException("expectedSequence");
            }

            if (watcher == null)
            {
                throw new ArgumentNullException("watcher");
            }

            if (watcher.IsDisposed)
            {
                throw new ArgumentException("EventWatcher must not be disposed.", "watcher");
            }            

            bool passed = true;

            int sequenceCount = expectedSequence.Count();

            if (sequenceCount > 0)
            {
                ReadOnlyCollection<IEvent> events = watcher.GetResults().ReceivedEvents;
                int eventsCount = events.Count;

                int firstIndex = -1;
                for (int i = 0; i < eventsCount; i++)
                {
                    if (string.Equals(expectedSequence[0], events[i].Name, StringComparison.CurrentCulture))
                    {
                        firstIndex = i;
                        break;                        
                    }                    
                }

                if (firstIndex >= 0)
                {
                    if (firstIndex + sequenceCount > eventsCount)
                    {
                        passed = false;
                    }
                    else
                    {
                        for (int i = 0; i < sequenceCount; i++)
                        {
                            string eventName1 = expectedSequence[i];
                            string eventName2 = events[firstIndex + i].Name;

                            if (string.Equals(eventName1, eventName2, StringComparison.CurrentCulture) == false)
                            {
                                passed = false;
                                break;
                            }
                        }
                    }
                }
                else
                {
                    passed = false;
                }

                if (passed == false)
                {
                    StringBuilder wholeSequence = new StringBuilder();
                    for (int i = 0; i < eventsCount; i++)
                    {
                        if (i > 0)
                        {
                            wholeSequence.Append(i == eventsCount - 1 ? " and " : ", ");
                        }
                        wholeSequence.Append(string.Format("<{0}>", events[i].Name));
                    }

                    AssertGateway.Fail(GetAssertFailedMessage(string.Format("EventAssert.WereRaisedInSequence failed. The whole sequence was {0}.", wholeSequence), message));                                                    
                }
            }
        }
    
        private static string GetAssertFailedMessage(string s1, string s2)
        {
            if (s2 == null)
            {
                return s1;
            }
            return string.Format("{0} {1}", s1, s2);
        }
    }
}
